use chrono::{Datelike, Timelike, Utc};

use std::cell::RefCell;
use std::sync::OnceLock;
use std::{fs, panic};

use android::ARRAYLIST_CLASS;
// This is the interface to the JVM that we'll call the majority of our
// methods on.
use jni::JNIEnv;
mod android;
// These objects are what you should use as arguments to your native
// function. They carry extra lifetime information to prevent them escaping
// this context and getting used after being GC'd.
use jni::objects::{JClass, JList, JObject, JString, JValue};

// This is just a pointer. We'll be returning it from our function. We
// can't return one of the objects with lifetime information because the
// lifetime checker won't let us.

use jni::sys::jobject;

use log::LevelFilter;
use markdown_elements::{Listener, ListenerDelegate, Markdown};
use markdown_parser::parse_with_listener;
struct AndroidMarkdownCallback<'a, 'b, 'local> {
    env: &'a mut JNIEnv<'local>,
    callback: &'a JObject<'local>,
    items: &'b mut Vec<JObject<'local>>,
}
impl<'a, 'b, 'local> Listener for AndroidMarkdownCallback<'a, 'b, 'local> {
    fn when(&mut self, item: &Markdown) {
        if matches!(item, Markdown::Nul) {
            return;
        }
        if self.callback.is_null() {
            return;
        }
        let android_markdown = convert_to_android(item, self.env);
        let _ = self.env.call_method(
            self.callback,
            "whenParseItem",
            "(Lcom/lly/markdown/MarkdownItem;)V",
            &[JValue::Object(&android_markdown)],
        );
        self.items.push(android_markdown)
    }
}
///
#[no_mangle]
pub extern "system" fn Java_com_lly_markdown_MarkdownServer_parse<'local>(
    mut env: JNIEnv<'local>,
    _class: JClass<'local>,
    data: JString<'local>,
    callback: JObject<'local>,
) -> jobject {
    let data: String = env.get_string(&data).unwrap().into();
    let mut items = Vec::new();
    let mut binding = AndroidMarkdownCallback {
        items: &mut items,
        env: &mut env,
        callback: &callback,
    };
    let listener = RefCell::new(ListenerDelegate::new(&mut binding));
    let (mut markdowns, errs) = parse_with_listener(&data, &listener);
    let mut ans: Vec<Markdown> = Vec::<Markdown>::new();
    let has_no_cache = callback.is_null() || items.is_empty();
    if has_no_cache {
        if let Some(markdowns) = &mut markdowns {
            ans.append(markdowns);
        }
    }
    if let Some(errs) = errs {
        ans.push(Markdown::Error { info: errs })
    }
    let markdowns = env.new_object(ARRAYLIST_CLASS, "()V", &[]).unwrap();
    let list = JList::from_env(&mut env, &markdowns).unwrap();
    if !has_no_cache {
        for ele in items {
            let _ = list.add(&mut env, &ele);
        }
    }
    for item in ans.iter().filter(|it| !matches!(it, Markdown::Nul)) {
        let item = convert_to_android(item, &mut env);
        let _ = list.add(&mut env, &item);
    }
    markdowns.as_raw()
}

fn convert_to_android<'local>(
    item: &Markdown,
    env: &mut JNIEnv<'local>,
) -> jni::objects::JObject<'local> {
    let item = match item {
        Markdown::Heading { level, content } => {
            android::generate_android_heading(env, *level, content)
        }
        Markdown::Text { content } => android::generate_android_text(env, content),
        Markdown::List { items, is_order } => android::generate_android_list(env, items, is_order),
        Markdown::Error { info } => android::generate_android_error(env, info),
        Markdown::Code { content } => android::generate_android_code(env, content),
        Markdown::Nul => panic!("不应该有Markdown::Nul"),
        Markdown::Table {
            heads,
            rows,
            aligns,
        } => android::generate_android_table(env, heads, rows, aligns),
        Markdown::CheckableList { items } => android::generate_android_checkable_list(env, items),
    };
    item
}

static LOGGER_PATH: OnceLock<String> = OnceLock::new();
///重定向崩溃信息
#[no_mangle]
pub extern "system" fn Java_com_lly_markdown_MarkdownServer_setPanic<'local>(
    mut env: JNIEnv<'local>,
    _class: JClass<'local>,
    path: JString<'local>,
) {
    android_logger::init_once(android_logger::Config::default().with_max_level(LevelFilter::Trace));
    log::debug!("{}", "setPanic");
    let path: String = env.get_string(&path).unwrap().into();
    LOGGER_PATH.get_or_init(|| path);
    panic::set_hook(Box::new(|info| {
        log::error!("{}", info);
        let time = Utc::now();
        let path = format!(
            "{}/markdown-{}-{}-{}-{}-{}-{}-{}.log",
            LOGGER_PATH.get().unwrap(),
            time.year(),
            time.month(),
            time.day(),
            time.hour(),
            time.minute(),
            time.second(),
            time.timestamp()
        );
        let _ = fs::write(path, info.to_string());
    }))
}
